#Cache
#Performs some data processing and stores incoming
#data in linked lists. Each torrent has its own linked
#list of data entries. Also has some cool and probably
#completely unnecessary mutexes.
#
#Written by Tony

import time

class Cache:
    def __init__(self):
        #key is the name and value is list of data entries
        self.torrents = {}
        self.lastUpdateTimes = {}
        self.server = LinkedList()
        self.EXPIRATION_TIME = 90
        self.mutex = 0        
        
    
    def deposit(self, torrentData):
        try:
            if (torrentData['name'] not in self.torrents):
                self.add_new_torrent(torrentData)
            else:
                try:
                    #creates new instance of DataEntry and adds it to the list of entries for that torrent
                    #will throw keyerror on the older test messages
                    self.torrents[torrentData['name']].append(DataEntry(torrentData['timestamp'], torrentData['transferred'], peers=torrentData['peers']))
                except KeyError:
                    self.torrents[torrentData['name']].append(DataEntry(torrentData['timestamp'], torrentData['transferred']))
                self.lastUpdateTimes[torrentData['name']] = time.time()
        #if a key error is thrown, we know it's a server message
        except KeyError:
            self.server.append(DataEntry(torrentData['timestamp'], torrentData, server=True))

#       Data expiration
        #delete = []
        #if (self.mutex == 0):
        #    self.mutex = 1
        #    for k,v in self.lastUpdateTimes.iteritems():
        #        if ((time.time() - v) > self.EXPIRATION_TIME):
        #            delete.append(k)
        #    self.mutex = 0
            
        #for torrent in delete:
        #    del self.lastUpdateTimes[torrent]
        #    del self.torrents[torrent]
                
            
            
    def add_new_torrent(self, torrentData):
        self.torrents.update({torrentData['name'] : LinkedList()})
        while (self.mutex == 1):
            self.mutex = 1
            self.lastUpdateTimes.update({torrentData['name'] : 0})
        else:
            self.mutex = 0
        self.deposit(torrentData)
        
    
    def get(self, torrent):
        """returns a reference to the linked list of torrent data
        entries for a single torrent or a list of torrents. 
        
        The linked list supports iteration in for loops.
        If a list of torrent is passed, then the references are returned 
        as a list. The ordering of the output list is the same as the
        input. 
        """
        if (type(torrent) is list):
            a = []
            for tor in torrent:
                a.append(self.torrents[tor])
            return a
        else:
            return self.torrents[torrent]
        
    def get_server_data(self):
        return self.server
        
    def get_torrent_names(self):
        """returns a list of names of all torrents in the cache"""
	keys = set(self.torrents.keys())
	old = []
	for key in keys:
	    if time.time() - self.lastUpdateTimes[key] > self.EXPIRATION_TIME:
	        old.append(key)
        keys = keys - set(old)
        return keys    
    
        
    
class DataEntry:
    def __init__(self, timestamp, data, peers=[],server=False):
        self.timestamp = timestamp
        if (server):
            self.data = data
        else:
            self.peers = peers
            self.data = {'upload_payload' : data[0], 'upload_protocol' : data[1], 'upload_ip_protocol' : data[2], 'upload_dht_protocol' : data[3], 'upload_tracker_protocol' : data[4],
                         'download_payload' : data[5], 'download_protocol' : data[6], 'download_ip_protocol' : data[7], 'download_dht_protocol' : data[8],
                         'download_tracker_protocol' : data [9]}
        self.next = None
        
class LinkedList:
    def __init__(self):
        self.first = None
        self.last = None
        self.size = 0
        self.position = self.first
        
    def __iter__(self):
        return self
    
    def next(self):
        if (self.position == self.last):
            self.position = self.first
            raise StopIteration
        tmp = self.position
        self.position = self.position.next
        return tmp
        
        
    def append(self, obj):
        """Adds the object to the end of the linked list"""
        if (self.first is None):
            self.first = obj
            self.position = obj
            self.last = obj
        else:
            self.last.next = obj
            self.last = obj
        self.size += 1
    
    def head(self):
        return self.first
    
    def tail(self):
        return self.last
    
    def remove(self, obj):
        if (self.first is obj):
            self.first = obj.next
            del obj
        else:
            current = self.first.next
            while(current.next != obj):
                if (current.next is None):
                    return 0
                current = current.next
            current.next = obj.next
            del obj
            
    
    
    
    
    
